本文將延續上一篇還沒講完的Model Validation,
繼續介紹ASP.Net Core MVC中自訂及遠端驗證的使用方式。
同步發表於個人點部落 - [鐵人賽Day16] ASP.Net Core MVC 進化之路 - Model Validation(2) / 自訂及遠端驗證
雖然預設已有許多實用的[ValidationAttribute],
但難免還是會遇到需要自訂驗證的時候。
這個部分跟ASP.Net MVC5沒有太大的改變,
透過繼承ValidationAttribute可以幫我們實作自訂的後端驗證,
即可迅速的打造一個屬於自己的[ValidationAttribute]。
因為預設並沒有提供判斷日期先後的Attribute,
所以筆者下方來實作一個[DateAfter]。
DateAfterAttribute.cs
public class DateAfterAttribute : ValidationAttribute
{
    private DateTime start;
    public DateAfterAttribute(string dateString, string format = "yyyy/MM/dd")
    {
        start = DateTime.ParseExact(dateString, format, null);
    }
    public override bool IsValid(object value)
    {
        var date = (DateTime)value;
        if (date.Ticks > start.Ticks)
        {
            return true;
        }
        return false;
    }
}
其實步驟相當簡單
首先繼承ValidationAttribute抽象類別,
複寫Valid()方法後就完成一半了。
要注意建構子中可定義使用時所需傳入的參數,
如[DateAfter("2020/1/1")],
建構子中第二個參數foramt使用預設值的方式,
可使日期格式較具彈性變化。Valid()中的參數value為前端表單欄位中要驗證的值,
因為型別為object所以記得要轉型。
使用時可以省略Attribute字樣(DataAfterAttribute),
最後來把它掛到要驗證的屬性上。
public class Book
{
    [BindRequired]
    public int Id { get; set; }
    [Required]
    public string Title{ get; set; }
    [DateAfter("2020/1/1", ErrorMessage = "your {0} should after 2020/1/1")]
    public DateTime PublishDate { get; set; }
}
測試結果如下。
要加入自訂的前端驗證步驟會稍稍麻煩一點,
首先要實作IClientValidator介面,
我們來修改上面的DateAfterAttribute。
public class DateAfterAttribute : ValidationAttribute, IClientModelValidator
{
    private DateTime start;
    public DateAfterAttribute(string dateString, string format = "yyyy/MM/dd")
    {
        start = DateTime.ParseExact(dateString, format, null);
    }
        
    public override bool IsValid(object value)
    {
        var date = (DateTime)value;
        if (date.Ticks > start.Ticks)
        {
            return true;
        }
        return false;
    }
    public void AddValidation(ClientModelValidationContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }
        //方式一
        MergeAttribute(context.Attributes, "data-val", "true");
        MergeAttribute(context.Attributes, "data-val-publishdate", "your publishdate should after 2020/12/30(前端驗證)");
        //方式二
        //context.Attributes["data-val"] = "true";
        //context.Attributes["data-val-publishdate"] = "your publishdate should after 2020/12/30(前端驗證)";
    }
    private bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
    {
        if (attributes.ContainsKey(key))
        {
            return false;
        }
        attributes.Add(key, value);
        return true;
    }
}
AddValidation方法只是在定義前端欄位的觸發條件及錯誤訊息,
盡量按照data-val-{property_name}的格式,
其中{property_name}要與前端設定的script相符。
除了透過MergeAttribute的方法,
也可以直接針對Context.Attributes操作(IDictionary)。
最後要在前端.cshtml加上一小段script。
@section scripts{
    <script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
    <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
    <script>
        $.validator.addMethod("publishdate",
            function (value, element, param) {
                var date = new Date(value);
                return date >new Date('2020/12/30');
            });
        $.validator.unobtrusive.adapters.addBool("publishdate");
    </script>
}
測試結果
遠端驗證(Remote)是什麼呢?
它是一種很像前端的後端驗證。
在執行過程不仔細觀察你會以為它是前端驗證(因為不會閃一下),
其原理是藉ajax方式與後端溝通。
常用於「檢查欄位名稱是否與資料庫重複」。
透過掛上[Remote] 就可以簡單完成,
驗證過程會呼叫遠端的Action,
最後透過回傳的Json格式比對驗證結果。
接著繼續針對Book修改,
假設書名(Title)是不能夠重複的,
我們這部分透過遠端驗證來實現。
public class Book 
{
    [BindRequired]
    public int Id { get; set; }
    [Remote(action: "CheckRepeatTitle", controller: "Sample")]
    [Required]
    public string Title{ get; set; }
        
    [DateAfter("2020/12/30", ErrorMessage = "your {0} should after 2020/12/30")]
    public DateTime PublishDate { get; set; }
    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }
}
最後在SampleController中CheckRepeatTitle實作方法。
public IActionResult CheckRepeatTitle(string Title)
{
    //Check title repeat state from database
    var isBookTitleRepeat = true;
    if (isBookTitleRepeat)
    {
        return Json($"{Title} is already in use.");
    }
    return Json(true);
}
大功告成後來測試一下結果。
遠端驗證使用起來其實非常爽,
如有需遠端驗證更進階應用的朋友可參考MSDN。
驗證的部分就先介紹到這邊,
如果內容有錯誤的在麻煩各位大神指正。
https://docs.microsoft.com/zh-tw/aspnet/core/mvc/models/validation?view=aspnetcore-2.1
http://www.binaryintellect.net/articles/d80b2b90-847b-4c5b-90ac-c2db18e131be.aspx